/*****************************************************************************

It's mini2440 network driver. (DM9000EP)
version:1.0

Bacon Xu
baconxu@gmail.com
2011.3.23



******************************************************************************/


#include <rtems.h>
#include <rtems/rtems_bsdnet.h>
#include <stdio.h>
#include <string.h>

#include <errno.h>
#include <rtems/error.h>

#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <net/if.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include <irq.h>

/*
It's micro for DM9000
*/
/*
 * dm9000 Ethernet
 */

#define DM9000_ID		        0x90000A46
#define DM9000_PKT_MAX		    1536	/* Received packet max size */
#define DM9000_PKT_RDY		    0x01	/* Packet ready to receive */

/* although the registers are 16 bit, they are 32-bit aligned.
 */

#define DM9000_NCR             	0x00
#define DM9000_NSR             	0x01
#define DM9000_TCR             	0x02
#define DM9000_TSR1            	0x03
#define DM9000_TSR2            	0x04
#define DM9000_RCR            	0x05
#define DM9000_RSR             	0x06
#define DM9000_ROCR            	0x07
#define DM9000_BPTR            	0x08
#define DM9000_FCTR            	0x09
#define DM9000_FCR             	0x0A
#define DM9000_EPCR            	0x0B
#define DM9000_EPAR            	0x0C
#define DM9000_EPDRL           	0x0D
#define DM9000_EPDRH           	0x0E
#define DM9000_WCR             	0x0F

#define DM9000_PAR             	0x10
#define DM9000_MAR             	0x16

#define DM9000_GPCR		        0x1e
#define DM9000_GPR             	0x1f
#define DM9000_TRPAL           	0x22
#define DM9000_TRPAH           	0x23
#define DM9000_RWPAL           	0x24
#define DM9000_RWPAH           	0x25

#define DM9000_VIDL            	0x28
#define DM9000_VIDH            	0x29
#define DM9000_PIDL            	0x2A
#define DM9000_PIDH            	0x2B

#define DM9000_CHIPR           	0x2C
#define DM9000_SMCR            	0x2F

#define DM9000_PHY		        0x40	/* PHY address 0x01 */

#define DM9000_MRCMDX          	0xF0
#define DM9000_MRCMD           	0xF2
#define DM9000_MRRL            	0xF4
#define DM9000_MRRH            	0xF5
#define DM9000_MWCMDX		    0xF6
#define DM9000_MWCMD           	0xF8
#define DM9000_MWRL            	0xFA
#define DM9000_MWRH            	0xFB
#define DM9000_TXPLL           	0xFC
#define DM9000_TXPLH           	0xFD
#define DM9000_ISR             	0xFE
#define DM9000_IMR             	0xFF

#define NCR_EXT_PHY		        (1<<7)
#define NCR_WAKEEN		        (1<<6)
#define NCR_FCOL		        (1<<4)
#define NCR_FDX			        (1<<3)
#define NCR_LBK			        (3<<1)
#define NCR_LBK_INT_MAC		    (1<<1)
#define NCR_LBK_INT_PHY		    (2<<1)
#define NCR_RST			        (1<<0)

#define NSR_SPEED		        (1<<7)
#define NSR_LINKST		        (1<<6)
#define NSR_WAKEST		        (1<<5)
#define NSR_TX2END		        (1<<3)
#define NSR_TX1END		        (1<<2)
#define NSR_RXOV		        (1<<1)

#define TCR_TJDIS		        (1<<6)
#define TCR_EXCECM		        (1<<5)
#define TCR_PAD_DIS2		    (1<<4)
#define TCR_CRC_DIS2		    (1<<3)
#define TCR_PAD_DIS1		    (1<<2)
#define TCR_CRC_DIS1		    (1<<1)
#define TCR_TXREQ		        (1<<0)

#define TSR_TJTO		        (1<<7)
#define TSR_LC			        (1<<6)
#define TSR_NC			        (1<<5)
#define TSR_LCOL		        (1<<4)
#define TSR_COL			        (1<<3)
#define TSR_EC			        (1<<2)

#define RCR_WTDIS		        (1<<6)
#define RCR_DIS_LONG		    (1<<5)
#define RCR_DIS_CRC		        (1<<4)
#define RCR_ALL			        (1<<3)
#define RCR_RUNT		        (1<<2)
#define RCR_PRMSC		        (1<<1)
#define RCR_RXEN		        (1<<0)

#define RSR_RF			        (1<<7)
#define RSR_MF			        (1<<6)
#define RSR_LCS			        (1<<5)
#define RSR_RWTO		        (1<<4)
#define RSR_PLE			        (1<<3)
#define RSR_AE			        (1<<2)
#define RSR_CE			        (1<<1)
#define RSR_FOE			        (1<<0)

#define EPCR_EPOS_PHY		    (1<<3)
#define EPCR_EPOS_EE		    (0<<3)
#define EPCR_ERPRR		        (1<<2)
#define EPCR_ERPRW		        (1<<1)
#define EPCR_ERRE		        (1<<0)

#define FCTR_HWOT(ot)		    (( (ot) & 0xf ) << 4 )
#define FCTR_LWOT(ot)		    ( (ot) & 0xf )

#define BPTR_BPHW(x)		    ((x) << 4)
#define BPTR_JPT_200US		    (0x07)
#define BPTR_JPT_600US		    (0x0f)

#define IMR_PAR			        (1<<7)
#define IMR_ROOM		        (1<<3)
#define IMR_ROM			        (1<<2)
#define IMR_PTM			        (1<<1)
#define IMR_PRM			        (1<<0)

#define ISR_ROOS		        (1<<3)
#define ISR_ROS			        (1<<2)
#define ISR_PTS			        (1<<1)
#define ISR_PRS			        (1<<0)

#define GPCR_GPIO0_OUT		    (1<<0)

#define GPR_PHY_PWROFF		    (1<<0)
/*  RTEMS event used by interrupt handler to start receive daemon. */
#define START_RECEIVE_EVENT  	RTEMS_EVENT_1

/* RTEMS event used to start transmit daemon. */
#define START_TRANSMIT_EVENT    RTEMS_EVENT_2



/* DM9000 network board routine ---------------------------- */

#define DM9000_outb(d,r) 	    (*(volatile uint8_t *)r = d)
#define DM9000_outw(d,r) 	    (*(volatile uint16_t *)r = d)
#define DM9000_outl(d,r) 	    (*(volatile uint32_t *)r = d)
#define DM9000_inb(r) 		    (*(volatile uint8_t *)r)
#define DM9000_inw(r) 		    (*(volatile uint16_t *)r)
#define DM9000_inl(r) 		    (*(volatile uint32_t *)r)
#define __le16_to_cpu(x)                    ((uint16_t)(x))

/* #define CONFIG_DM9000_DEBUG */

#ifdef CONFIG_DM9000_DEBUG
#define DM9000_DBG(fmt,args...) printf(fmt, ##args)
#define DM9000_DMP_PACKET(func,packet,length)  \
    do { \
        int i;                          \
        printf(func ": length: %d\n", length);          \
        for (i = 0; i < length; i++) {              \
            if (i % 8 == 0)                 \
                printf("\n%s: %02x: ", func, i);    \
            printf("%02x ", ((unsigned char *) packet)[i]); \
        } printf("\n");                     \
    } while(0)
#else
#define DM9000_DBG(fmt,args...)
#define DM9000_DMP_PACKET(func,packet,length)
#endif


#define CONFIG_DRIVER_DM9000                1
#define CONFIG_DM9000_BASE                  0x20000300
#define DM9000_IO                           CONFIG_DM9000_BASE
#define DM9000_DATA                         (CONFIG_DM9000_BASE + 4)


/*
#define CONFIG_NET_POLL_CONTROLLER
*/
/*
#define CONFIG_DM9000_USE_8BIT
*/
#define CONFIG_DM9000_USE_16BIT
/*
#define CONFIG_DM9000_USE_32BIT
*/
#define CONFIG_DM9000_MAC_ADDR_H            0x0012
#define CONFIG_DM9000_MAC_ADDR_L            0x34567800



/*****************************************************************************

global type for dm9000

*****************************************************************************/
/*
 * Hardware-specific storage
 */
typedef struct
{
    /*
     * Connection to networking code
     * This entry *must* be the first in the sonic_softc structure.
     */
    struct arpcom                    arpcom;
    /*
     * Interrupt vector
     */
    rtems_vector_number             vector;
    /*
     *  Indicates configuration
     */
    int                             acceptBroadcast;
    /*
     * Task waiting for interrupts
     */
    rtems_id                        rxDaemonTid;
    rtems_id                        txDaemonTid;

    uint32_t                        (*outmbuf)(struct mbuf *);
    void                            (*outblk)(volatile void *, int);
    void                            (*inblk)(void *, int);
    void                            (*rx_status)(uint16_t *, uint16_t *);
#if 0
    /*
     * Statistics
     */
    unsigned long                   Interrupts;
    unsigned long                   rxInterrupts;
    unsigned long                   rxMissed;
    unsigned long                   rxGiant;
    unsigned long                   rxNonOctet;
    unsigned long                   rxBadCRC;
    unsigned long                   rxCollision;

    unsigned long                   txInterrupts;
    unsigned long                   txSingleCollision;
    unsigned long                   txMultipleCollision;
    unsigned long                   txCollision;
    unsigned long                   txDeferred;
    unsigned long                   txUnderrun;
    unsigned long                   txLateCollision;
    unsigned long                   txExcessiveCollision;
    unsigned long                   txExcessiveDeferral;
    unsigned long                   txLostCarrier;
    unsigned long                   txRawWait;
#endif
} dm9000_softc_t;

/*****************************************************************************

internal variables.

*****************************************************************************/
static dm9000_softc_t softc;


/*****************************************************************************

internal functions

*****************************************************************************/
static uint32_t dm9000_outmbuf_8bit(struct mbuf *m);
static uint32_t dm9000_outmbuf_16bit(struct mbuf *m);
static uint32_t dm9000_outmbuf_32bit(struct mbuf *m);
static void dm9000_outblk_8bit(volatile void *data_ptr, int count);
static void dm9000_outblk_16bit(volatile void *data_ptr, int count);
static void dm9000_outblk_32bit(volatile void *data_ptr, int count);
static void dm9000_inblk_8bit(void *data_ptr, int count);
static void dm9000_inblk_16bit(void *data_ptr, int count);
static void dm9000_inblk_32bit(void *data_ptr, int count);
static void dm9000_rx_status_32bit(uint16_t *RxStatus, uint16_t *RxLen);
static void dm9000_rx_status_16bit(uint16_t *RxStatus, uint16_t *RxLen);
static void dm9000_rx_status_8bit(uint16_t *RxStatus, uint16_t *RxLen);
static uint8_t DM9000_ior(int reg);
static void DM9000_iow(int reg, uint8_t value);
static uint16_t phy_read(int reg);
static void phy_write(int reg, uint16_t value);
static void udelay(uint32_t us);
#ifdef CONFIG_DM9000_DEBUG
static void dump_regs(void);
#endif
static int dm9000_set_mac(dm9000_softc_t *sc);
static void dm9000_reset(void);
static int dm9000_probe(void);

void dm9000_init(void *arg);
int dm9000_init_hw(dm9000_softc_t *sc);
void dm9000_start(struct ifnet *ifp);
void dm9000_stop(dm9000_softc_t *sc);
void dm9000_txDaemon(void *arg);
void dm9000_sendpacket(dm9000_softc_t *sc, struct mbuf *m);
void dm9000_rxDaemon(void *arg);
void dm9000_stats (dm9000_softc_t *sc);
static void dm9000_isr_on(const rtems_irq_connect_data *unused);
static void dm9000_isr_off(const rtems_irq_connect_data *unused);
static int dm9000_isr_is_on(const rtems_irq_connect_data *irq);
rtems_isr dm9000_isr (rtems_vector_number v);
static int dm9000_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data);

/* Replace the first value with the clock's interrupt name. */
rtems_irq_connect_data dm9000_isr_data = {
    BSP_EINT4_7,
    (rtems_irq_hdl)dm9000_isr,
    dm9000_isr_on,
    dm9000_isr_off,
    dm9000_isr_is_on,
    3,    /* unused for ARM */
    0 };  /* unused for ARM */

/*****************************************************************************

*****************************************************************************/
int rtems_dm9000_attach (
    struct rtems_bsdnet_ifconfig *config,
    void *chip  /* only one ethernet, so no chip number */
    )
{
    struct ifnet *ifp;
    int mtu;
    int unitnumber;
    char *unitname;

    /*
     * Parse driver name
     */
    if ((unitnumber = rtems_bsdnet_parse_driver_name (config, &unitname)) < 0)
        return 0;

    /*
     * Is driver free?
     */
    if (unitnumber != 0) {
        printf ("Bad DM9000 unit number.\n");
        return 0;
    }

    ifp = &softc.arpcom.ac_if;
    if (ifp->if_softc != NULL) {
        printf ("Driver already in use.\n");
        return 0;
    }

    /*
     *  zero out the control structure
     */

    memset( &softc, 0, sizeof(softc) );


    /* get the MAC address from the chip */
    softc.arpcom.ac_enaddr[0] = 0x00;
    softc.arpcom.ac_enaddr[1] = 0x00;
    softc.arpcom.ac_enaddr[2] = 0x01;
    softc.arpcom.ac_enaddr[3] = 0x23;
    softc.arpcom.ac_enaddr[4] = 0x45;
    softc.arpcom.ac_enaddr[5] = 0x00;


    if (config->mtu) {
        mtu = config->mtu;
    } else {
        mtu = ETHERMTU;
    }

    softc.acceptBroadcast = !config->ignore_broadcast;

    /*
     * Set up network interface values
     */
    ifp->if_softc = &softc;
    ifp->if_unit = unitnumber;
    ifp->if_name = unitname;
    ifp->if_mtu = mtu;
    ifp->if_init = dm9000_init;
    ifp->if_ioctl = dm9000_ioctl;
    ifp->if_start = dm9000_start;
    ifp->if_output = ether_output;
    ifp->if_flags = IFF_BROADCAST;
    if (ifp->if_snd.ifq_maxlen == 0) {
        ifp->if_snd.ifq_maxlen = ifqmaxlen;
    }

    /*
     * Attach the interface
     */
    if_attach (ifp);
    ether_ifattach (ifp);
    return 1;
}

#ifdef CONFIG_NET_POLL_CONTROLLER
rtems_id gID;
rtems_name gName;

rtems_task dm9000_poll_task(rtems_task_argument ignored)
{
    while (1)
    {
        dm9000_isr(1);
        rtems_task_wake_after(5);
    }
}
#endif

void dm9000_init(void *arg)
{
    dm9000_softc_t     *sc = arg;
    struct ifnet *ifp = &sc->arpcom.ac_if;

    /* 
     *This is for stuff that only gets done once (at91rm9200_emac_init() 
     * gets called multiple times 
     */
    if (sc->txDaemonTid == 0) {
        /* Set up EMAC hardware */
        dm9000_init_hw(sc);

        /*      Start driver tasks */
        sc->rxDaemonTid = rtems_bsdnet_newproc("ENrx", 
                                               4096, 
                                               dm9000_rxDaemon, 
                                               sc);
        sc->txDaemonTid = rtems_bsdnet_newproc("ENtx", 
                                               4096, 
                                               dm9000_txDaemon, 
                                               sc);
#ifdef CONFIG_NET_POLL_CONTROLLER
        gName = rtems_build_name('D', 'E', 'A', 'M');
        rtems_task_create(gName, 1, RTEMS_MINIMUM_STACK_SIZE * 2, 
                            RTEMS_DEFAULT_MODES, 
                            RTEMS_DEFAULT_ATTRIBUTES, 
                            &gID);
        rtems_task_start(gID, dm9000_poll_task, 1);
#endif
    } /* if txDaemonTid */

#if !defined(CONFIG_NET_POLL_CONTROLLER)
    /* install the interrupt handler */
    BSP_install_rtems_irq_handler(&dm9000_isr_data);
#endif

    /* EMAC doesn't support promiscuous, so ignore requests */
    if (ifp->if_flags & IFF_PROMISC) {
        printf ("Warning - DM9000 Ethernet driver"
                " doesn't support Promiscuous Mode!\n");
    }

    /*
     * Tell the world that we're running.
     */
    ifp->if_flags |= IFF_RUNNING;
}

int  dm9000_init_hw(dm9000_softc_t *sc)
{
    uint8_t io_mode;
    int i, lnk;

    /* RESET device */
    dm9000_reset();

    if (dm9000_probe() < 0)
        return -1;

    /* Auto-detect 8/16/32 bit mode, ISR Bit 6+7 indicate bus width */
#if defined(CONFIG_DM9000_USE_16BIT)
    io_mode = 0;
#else
    io_mode = DM9000_ior(DM9000_ISR) >> 6;
#endif

    switch (io_mode) {
    case 0x0:  /* 16-bit mode */
        printf("DM9000: running in 16 bit mode\n");
        sc->outmbuf   = dm9000_outmbuf_16bit;
        sc->outblk    = dm9000_outblk_16bit;
        sc->inblk     = dm9000_inblk_16bit;
        sc->rx_status = dm9000_rx_status_16bit;
        break;
    case 0x01:  /* 32-bit mode */
        printf("DM9000: running in 32 bit mode\n");
        sc->outmbuf   = dm9000_outmbuf_32bit;
        sc->outblk    = dm9000_outblk_32bit;
        sc->inblk     = dm9000_inblk_32bit;
        sc->rx_status = dm9000_rx_status_32bit;
        break;
    case 0x02: /* 8 bit mode */
        printf("DM9000: running in 8 bit mode\n");
        sc->outmbuf   = dm9000_outmbuf_8bit;
        sc->outblk    = dm9000_outblk_8bit;
        sc->inblk     = dm9000_inblk_8bit;
        sc->rx_status = dm9000_rx_status_8bit;
        break;
    default:
        /* Assume 8 bit mode, will probably not work anyway */
        printf("DM9000: Undefined IO-mode:0x%x\n", io_mode);
        sc->outmbuf   = dm9000_outmbuf_8bit;
        sc->outblk    = dm9000_outblk_8bit;
        sc->inblk     = dm9000_inblk_8bit;
        sc->rx_status = dm9000_rx_status_8bit;
        break;
    }

    /* Program operating register, only internal phy supported */
    DM9000_iow(DM9000_NCR, 0x0);
    /* TX Polling clear */
    DM9000_iow(DM9000_TCR, 0);
    /* Less 3Kb, 200us */
    DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_200US);
    /* Flow Control : High/Low Water */
    DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
    /* SH FIXME: This looks strange! Flow Control */
    DM9000_iow(DM9000_FCR, 0x0);
    /* Special Mode */
    DM9000_iow(DM9000_SMCR, 0);
    /* clear TX status */
    DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
    /* Clear interrupt status */
    DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);

    /*set mac address*/
    dm9000_set_mac(sc);

    /* Activate DM9000 */
    /* RX enable */
    DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
    /* Enable TX/RX interrupt mask */
    DM9000_iow(DM9000_IMR, IMR_PAR | IMR_PRM);


    /* PHY read status doesn't work anyway*/
#if !defined(CONFIG_MINI2440)
    return 0;
#endif
    i = 0;
    while (!(phy_read(1) & 0x20)) { /* autonegation complete bit */
        udelay(1000);
        i++;
        if (i == 10000) {
            printf("could not establish link\n");
            return 0;
        }
    }

    /* see what we've got */
    lnk = phy_read(17) >> 12;
    printf("operating at ");
    switch (lnk) {
    case 1:
        printf("10M half duplex ");
        break;
    case 2:
        printf("10M full duplex ");
        break;
    case 4:
        printf("100M half duplex ");
        break;
    case 8:
        printf("100M full duplex ");
        break;
    default:
        printf("unknown: %d ", lnk);
        break;
    }
    printf("mode\n");
    return 0;
}

void dm9000_start(struct ifnet *ifp)
{
    dm9000_softc_t *sc = ifp->if_softc;

    rtems_event_send(sc->txDaemonTid, START_TRANSMIT_EVENT);
    ifp->if_flags |= IFF_OACTIVE;
}

void dm9000_stop (dm9000_softc_t *sc)
{
    struct ifnet *ifp = &sc->arpcom.ac_if;

    ifp->if_flags &= ~IFF_RUNNING;

    /*
     * Stop the transmitter and receiver.
     */
    DM9000_iow(DM9000_RCR, 0x00);   /* Disable RX */
}

/*
 * Driver transmit daemon
 */
void dm9000_txDaemon (void *arg)
{
    dm9000_softc_t *sc = (dm9000_softc_t *)arg;
    struct ifnet *ifp = &sc->arpcom.ac_if;
    struct mbuf *m;
    rtems_event_set events;

    for (;;)
    {
        /* turn on TX interrupt, then wait for one */
        rtems_bsdnet_event_receive(
            START_TRANSMIT_EVENT,
            RTEMS_EVENT_ANY | RTEMS_WAIT,
            RTEMS_NO_TIMEOUT,
            &events);

        /* Send packets till queue is empty */
        for (;;)
        {
            /* Get the next mbuf chain to transmit. */
            IF_DEQUEUE(&ifp->if_snd, m);
            if (!m)
                break;
            dm9000_sendpacket(sc, m);
        }
        ifp->if_flags &= ~IFF_OACTIVE;
    }
}

/* Send packet */
void dm9000_sendpacket(dm9000_softc_t *sc, struct mbuf *m)
{
    unsigned int length = 0;
    rtems_interval tmo;
#if 0
    struct mbuf *l;
    static uint8_t packet[2000];

    /* copy the mbuf chain into the transmit buffer */
    l = m;
    while (l != NULL) {
        memcpy(((char *)packet + length),  /* offset into pkt for mbuf */ 
               (char *)mtod(l, void *),       /* cast to void */ 
               l->m_len);                     /* length of this mbuf */

        length += l->m_len;               /* update offset */
        l = l->m_next;                        /* get next mbuf, if any */     
    }

    /* free the mbuf chain we just copied */
    m_freem(m);

    DM9000_DMP_PACKET("eth_send", packet, length);

    /* Move data to DM9000 TX RAM */
    DM9000_outb(DM9000_MWCMD, DM9000_IO); /* Prepare for TX-data */

    /* push the data to the TX-fifo */
    (sc->outblk)(packet, length);
#else
    /* Move data to DM9000 TX RAM */
    DM9000_outb(DM9000_MWCMD, DM9000_IO); /* Prepare for TX-data */

    length = (sc->outmbuf)(m);

    m_freem(m);
#endif

    /* Set TX length to DM9000 */
    DM9000_iow(DM9000_TXPLL, length & 0xff);
    DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);

    /* Issue TX polling command */
    DM9000_iow(DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */

    /* wait for end of transmission */
    tmo = rtems_clock_get_ticks_since_boot() + 5 * rtems_clock_get_ticks_per_second();
    while ( !(DM9000_ior(DM9000_NSR) & (NSR_TX1END | NSR_TX2END)) ||
        !(DM9000_ior(DM9000_ISR) & IMR_PTM) ) {
        if (rtems_clock_get_ticks_since_boot() >= tmo)
        {
            printf("transmission timeout\n");
            break;
        }
    }
    DM9000_iow(DM9000_ISR, IMR_PTM); /* Clear Tx bit in ISR */

    DM9000_DBG("transmit done\n\n");
}


/* dm9000 reader task */
void dm9000_rxDaemon(void *arg)
{
    dm9000_softc_t *sc = (dm9000_softc_t *)arg;
    struct ifnet *ifp = &sc->arpcom.ac_if;
    struct mbuf *m;
    struct ether_header *eh;
    rtems_event_set events;
    uint8_t rxbyte;
    uint16_t RxStatus, RxLen = 0;

    /* Input packet handling loop */
    for (;;) {
        rtems_bsdnet_event_receive(
            START_RECEIVE_EVENT,
            RTEMS_EVENT_ANY | RTEMS_WAIT,
            RTEMS_NO_TIMEOUT,
            &events);

        /* There is _at least_ 1 package in the fifo, read them all */
        for (;;) {
            /* get an mbuf this packet */
            MGETHDR(m, M_WAIT, MT_DATA);

            /* now get a cluster pointed to by the mbuf */
            /* since an mbuf by itself is too small */
            MCLGET(m, M_WAIT);

            /* set the type of mbuf to ifp (ethernet I/F) */
            m->m_pkthdr.rcvif = ifp;
            m->m_nextpkt = 0;

            DM9000_ior(DM9000_MRCMDX);  /* Dummy read */

            /* Get most updated data,
                only look at bits 0:1, See application notes DM9000 */
            rxbyte = DM9000_inb(DM9000_DATA) & 0x03;

            /* Status check: this byte must be 0 or 1 */
            if (rxbyte > DM9000_PKT_RDY) {
                DM9000_iow(DM9000_RCR, 0x00);   /* Stop Device */
                DM9000_iow(DM9000_ISR, 0x80);   /* Stop INT request */
                printf("DM9000 error: status check fail: 0x%x\n",
                    rxbyte);
                m_freem(m);
                rtems_task_wake_after(2 * rtems_clock_get_ticks_per_second());
                dm9000_init_hw(sc);
                break;
            }

            if (rxbyte != DM9000_PKT_RDY)
            {
                m_freem(m);
                break; /* No packet received, ignore */
            }
            DM9000_DBG("receiving packet\n");

            DM9000_outb(DM9000_MWCMD, DM9000_IO);
            /* A packet ready now  & Get status/length */
            (sc->rx_status)(&RxStatus, &RxLen);

            DM9000_DBG("rx status: 0x%04x rx len: %d\n", RxStatus, RxLen);

            if ((RxStatus & 0xbf00) || (RxLen < 0x40)
                || (RxLen > DM9000_PKT_MAX)) {
                if (RxStatus & 0x100) {
                    printf("rx fifo error\n");
                }
                if (RxStatus & 0x200) {
                    printf("rx crc error\n");
                }
                if (RxStatus & 0x8000) {
                    printf("rx length error\n");
                }
                if (RxLen > DM9000_PKT_MAX) {
                    printf("rx length too big\n");
                    /*dm9000_reset();*/
                    dm9000_init_hw(sc);
                }
                m_freem(m);
            } else {
                /* Move data from DM9000 */
                /* Read received packet from RX SRAM */
                (sc->inblk)(m->m_ext.ext_buf, RxLen);
                DM9000_DMP_PACKET("eth_rx", rdptr, RxLen);

                DM9000_DBG("passing packet to upper layer\n");

                /* set the length of the mbuf */
                m->m_len = RxLen - (sizeof(struct ether_header) + 4);
                m->m_pkthdr.len = m->m_len;

                /* strip off the ethernet header from the mbuf */
                /* but save the pointer to it */
                eh = mtod (m, struct ether_header *);
                m->m_data += sizeof(struct ether_header);
                ether_input(ifp, eh, m);
            }
        }
    } /* for (;;) */
}

/* Show interface statistics */
void dm9000_stats (dm9000_softc_t *sc)
{
    printf("dm9000_stats isn't sound.\n");
}

static void dm9000_isr_on(const rtems_irq_connect_data *unused)
{

    rEXTINT0 = 0x12222222;
    /*Initialize the interrupt, mini2440 eint7*/
    /*eint4_7 use irq mode*/
    rINTMOD &= (~BIT_EINT4_7);
    /*enable eint7, io -> eint7*/
    rGPFCON = ((rGPFCON & 0x3FFF) | (0x2 << 14));
    /*enable eint7*/
    rEINTMASK &= (~0x80);
    /*enable eint4-7*/
    rINTMSK &= (~BIT_EINT4_7);
    return;
}


static void dm9000_isr_off(const rtems_irq_connect_data *unused)
{
    /* disable all various TX/RX interrupts */
    rINTMSK |= BIT_EINT4_7; 
    rEINTMASK |= 0x80;
    return;
}

/* Tests to see if dm9000 interrupts are enabled, and 
 * returns non-0 if so.
 * If interrupt is not enabled, returns 0.
 */
static int dm9000_isr_is_on(const rtems_irq_connect_data *irq)
{
    return ((rINTMSK & BIT_EINT4_7) == 0)? 1:0; /* any interrupts enabled? */
}

/* interrupt handler */
rtems_isr dm9000_isr (rtems_vector_number v)
{
#if !defined(CONFIG_NET_POLL_CONTROLLER)
    rEINTMASK |= 0x80;
#endif
    if ((DM9000_ior(DM9000_ISR) & 0x01))
    {
        rtems_event_send (softc.rxDaemonTid, START_RECEIVE_EVENT);
        DM9000_iow(DM9000_ISR, 0x01);
    }

#if !defined(CONFIG_NET_POLL_CONTROLLER)
    rEINTPEND = 0x80;
    ClearPending(BIT_EINT4_7);
    rEINTMASK &= ~0x80;
#endif
}

/*  Driver ioctl handler */
static int
dm9000_ioctl (struct ifnet *ifp, ioctl_command_t command, caddr_t data)
{
    dm9000_softc_t *sc = ifp->if_softc;
    int error = 0;

    switch (command) {
    case SIOCGIFADDR:
    case SIOCSIFADDR:
        ether_ioctl (ifp, command, data);
        break;

    case SIOCSIFFLAGS:
        switch (ifp->if_flags & (IFF_UP | IFF_RUNNING))
        {
        case IFF_RUNNING:
            dm9000_stop (sc);
            break;

        case IFF_UP:
            dm9000_init (sc);
            break;

        case IFF_UP | IFF_RUNNING:
            dm9000_stop (sc);
            dm9000_init (sc);
            break;

        default:
            break;
        } /* switch (if_flags) */
        break;

    case SIO_RTEMS_SHOW_STATS:
        dm9000_stats (sc);
        break;

        /*
         * FIXME: All sorts of multicast commands need to be added here!
         */
    default:
        error = EINVAL;
        break;
    } /* switch (command) */
    return error;
}

static uint32_t dm9000_outmbuf_8bit(struct mbuf *m)
{
    struct mbuf *l;
    uint32_t length = 0;

    l = m;
    while (l != NULL)
    {
        if (l->m_len > 0)
            dm9000_outblk_8bit(mtod(l, void *), l->m_len);

        length += l->m_len;               /* update offset */
        l = l->m_next;                        /* get next mbuf, if any */     
    }
    return (length);
}


static uint32_t dm9000_outmbuf_16bit(struct mbuf *m)
{
    uint32_t offset = 0;
    uint32_t len;
    uint8_t data[2];
    uint8_t *ptr;
    uint32_t length = 0;
    struct mbuf *l;

    l = m;
    while (l != NULL)
    {
        if (l->m_len > 0)
        {
            ptr = ((uint8_t *)mtod(l, void *));
            len = l->m_len - offset;

            if (offset == 1)
            {
                data[1] = *ptr;
                dm9000_outblk_16bit(data, 2);
            }

            dm9000_outblk_16bit((ptr + offset), (len & 0xFFFFFFFE));
            offset = len & 0x1;

            if (offset)
            {
                data[0] = *(ptr + l->m_len - 1);
            }
        }
        length += l->m_len;
        l = l->m_next;
    }

    if (offset)
        dm9000_outblk_16bit(data, 1);

    return (length);
}


static uint32_t dm9000_outmbuf_32bit(struct mbuf *m)
{
    struct mbuf *l;
    uint32_t length = 0;
    uint8_t packet[2000];

    l = m;
    while (l != NULL) {
        memcpy(((char *)packet + length),  /* offset into pkt for mbuf */ 
               (char *)mtod(l, void *),       /* cast to void */ 
               l->m_len);                     /* length of this mbuf */

        length += l->m_len;               /* update offset */
        l = l->m_next;                        /* get next mbuf, if any */     
    }
    dm9000_outblk_32bit(packet, length);
    return (length);
}

static void dm9000_outblk_8bit(volatile void *data_ptr, int count)
{
	int i;
	for (i = 0; i < count; i++)
		DM9000_outb((((uint8_t *) data_ptr)[i] & 0xff), DM9000_DATA);
}

static void dm9000_outblk_16bit(volatile void *data_ptr, int count)
{
	int i;
	uint32_t tmplen = (count + 1) / 2;

	for (i = 0; i < tmplen; i++)
		DM9000_outw(((uint16_t *) data_ptr)[i], DM9000_DATA);
}
static void dm9000_outblk_32bit(volatile void *data_ptr, int count)
{
	int i;
	uint32_t tmplen = (count + 3) / 4;

	for (i = 0; i < tmplen; i++)
		DM9000_outl(((uint32_t *) data_ptr)[i], DM9000_DATA);
}

static void dm9000_inblk_8bit(void *data_ptr, int count)
{
	int i;
	for (i = 0; i < count; i++)
		((uint8_t *) data_ptr)[i] = DM9000_inb(DM9000_DATA);
}

static void dm9000_inblk_16bit(void *data_ptr, int count)
{
	int i;
	uint32_t tmplen = (count + 1) / 2;

	for (i = 0; i < tmplen; i++)
		((uint16_t *) data_ptr)[i] = DM9000_inw(DM9000_DATA);
}
static void dm9000_inblk_32bit(void *data_ptr, int count)
{
	int i;
	uint32_t tmplen = (count + 3) / 4;

	for (i = 0; i < tmplen; i++)
		((uint32_t *) data_ptr)[i] = DM9000_inl(DM9000_DATA);
}

static void dm9000_rx_status_32bit(uint16_t *RxStatus, uint16_t *RxLen)
{
	uint32_t tmpdata;

	DM9000_outb(DM9000_MRCMD, DM9000_IO);

	tmpdata = DM9000_inl(DM9000_DATA);
	*RxStatus = __le16_to_cpu(tmpdata);
	*RxLen = __le16_to_cpu(tmpdata >> 16);
}

static void dm9000_rx_status_16bit(uint16_t *RxStatus, uint16_t *RxLen)
{
	DM9000_outb(DM9000_MRCMD, DM9000_IO);

	*RxStatus = __le16_to_cpu(DM9000_inw(DM9000_DATA));
	*RxLen = __le16_to_cpu(DM9000_inw(DM9000_DATA));
}

static void dm9000_rx_status_8bit(uint16_t *RxStatus, uint16_t *RxLen)
{
	DM9000_outb(DM9000_MRCMD, DM9000_IO);

	*RxStatus =
	    __le16_to_cpu(DM9000_inb(DM9000_DATA) +
			  (DM9000_inb(DM9000_DATA) << 8));
	*RxLen =
	    __le16_to_cpu(DM9000_inb(DM9000_DATA) +
			  (DM9000_inb(DM9000_DATA) << 8));
}

/*
   Read a byte from I/O port
*/
static uint8_t
DM9000_ior(int reg)
{
    DM9000_outb(reg, DM9000_IO);
    return DM9000_inb(DM9000_DATA);
}

/*
   Write a byte to I/O port
*/
static void
DM9000_iow(int reg, uint8_t value)
{
    DM9000_outb(reg, DM9000_IO);
    DM9000_outb(value, DM9000_DATA);
}

/*
   Read a word from phyxcer
*/
static uint16_t
phy_read(int reg)
{
    uint16_t val;

    /* Fill the phyxcer register into REG_0C */
    DM9000_iow(DM9000_EPAR, DM9000_PHY | reg);
    DM9000_iow(DM9000_EPCR, 0xc);   /* Issue phyxcer read command */
    udelay(100);            /* Wait read complete */
    DM9000_iow(DM9000_EPCR, 0x0);   /* Clear phyxcer read command */
    val = (DM9000_ior(DM9000_EPDRH) << 8) | DM9000_ior(DM9000_EPDRL);

    /* The read data keeps on REG_0D & REG_0E */
    DM9000_DBG("phy_read(0x%x): 0x%x\n", reg, val);
    return val;
}

/*
   Write a word to phyxcer
*/
static void
phy_write(int reg, uint16_t value)
{
    /* Fill the phyxcer register into REG_0C */
    DM9000_iow(DM9000_EPAR, DM9000_PHY | reg);

    /* Fill the written data into REG_0D & REG_0E */
    DM9000_iow(DM9000_EPDRL, (value & 0xff));
    DM9000_iow(DM9000_EPDRH, ((value >> 8) & 0xff));
    DM9000_iow(DM9000_EPCR, 0xa);   /* Issue phyxcer write command */
    udelay(500);            /* Wait write complete */
    DM9000_iow(DM9000_EPCR, 0x0);   /* Clear phyxcer write command */
    DM9000_DBG("phy_write(reg:0x%x, value:0x%x)\n", reg, value);
}

static void udelay(uint32_t us)
{
    uint32_t i, j;

    for (i = 0; i < us; i++)
        for (j = 0; j < 1000; j++)
            asm volatile("nop");
}

#ifdef CONFIG_DM9000_DEBUG
static void
dump_regs(void)
{
    DM9000_DBG("\n");
    DM9000_DBG("NCR   (0x00): %02x\n", DM9000_ior(0));
    DM9000_DBG("NSR   (0x01): %02x\n", DM9000_ior(1));
    DM9000_DBG("TCR   (0x02): %02x\n", DM9000_ior(2));
    DM9000_DBG("TSRI  (0x03): %02x\n", DM9000_ior(3));
    DM9000_DBG("TSRII (0x04): %02x\n", DM9000_ior(4));
    DM9000_DBG("RCR   (0x05): %02x\n", DM9000_ior(5));
    DM9000_DBG("RSR   (0x06): %02x\n", DM9000_ior(6));
    DM9000_DBG("ISR   (0xFE): %02x\n", DM9000_ior(DM9000_ISR));
    DM9000_DBG("\n");
}
#endif

/*
  Search DM9000 board, allocate space and register it
*/
int
dm9000_probe(void)
{
    uint32_t id_val;
    id_val = DM9000_ior(DM9000_VIDL);
    id_val |= DM9000_ior(DM9000_VIDH) << 8;
    id_val |= DM9000_ior(DM9000_PIDL) << 16;
    id_val |= DM9000_ior(DM9000_PIDH) << 24;
    if (id_val == DM9000_ID) {
        printf("dm9000 i/o: 0x%x, id: 0x%x \n", CONFIG_DM9000_BASE,
               id_val);
        return 0;
    } else {
        printf("dm9000 not found at 0x%08x id: 0x%08x\n",
               CONFIG_DM9000_BASE, id_val);
        return -1;
    }
}

/* General Purpose dm9000 reset routine */
static void
dm9000_reset(void)
{
    DM9000_DBG("resetting DM9000\n");

    /* Reset DM9000,
       see DM9000 Application Notes V1.22 Jun 11, 2004 page 29 */

    /* DEBUG: Make all GPIO0 outputs, all others inputs */
    DM9000_iow(DM9000_GPCR, GPCR_GPIO0_OUT);
    /* Step 1: Power internal PHY by writing 0 to GPIO0 pin */
    DM9000_iow(DM9000_GPR, 0);
    /* Step 2: Software reset */
    DM9000_iow(DM9000_NCR, (NCR_LBK_INT_MAC | NCR_RST));

    do {
        DM9000_DBG("resetting the DM9000, 1st reset\n");
        udelay(25); /* Wait at least 20 us */
    } while (DM9000_ior(DM9000_NCR) & 1);

    DM9000_iow(DM9000_NCR, 0);
    DM9000_iow(DM9000_NCR, (NCR_LBK_INT_MAC | NCR_RST)); /* Issue a second reset */

    do {
        DM9000_DBG("resetting the DM9000, 2nd reset\n");
        udelay(25); /* Wait at least 20 us */
    } while (DM9000_ior(DM9000_NCR) & 1);

    /* Check whether the ethernet controller is present */
    if ((DM9000_ior(DM9000_PIDL) != 0x0) ||
        (DM9000_ior(DM9000_PIDH) != 0x90))
        printf("ERROR: resetting DM9000 -> not responding\n");
}

/* Initilize dm9000 MAC, without reset
*/
int dm9000_set_mac(dm9000_softc_t *sc)
{
    int i,oft;
    printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", sc->arpcom.ac_enaddr[0],
           sc->arpcom.ac_enaddr[1], sc->arpcom.ac_enaddr[2], sc->arpcom.ac_enaddr[3],
           sc->arpcom.ac_enaddr[4], sc->arpcom.ac_enaddr[5]);

    /* fill device MAC address registers */
    for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
        DM9000_iow(oft, sc->arpcom.ac_enaddr[i]);
    for (i = 0, oft = 0x16; i < 8; i++, oft++)
        DM9000_iow(oft, 0xff);

    /* read back mac, just to be sure */
    for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
        DM9000_DBG("%02x:", DM9000_ior(oft));
    DM9000_DBG("\n");

    return 0;
}